use crate::{
    ast::{types, Expression, Type},
    error::{CompilerError, Result},
    llvm_backend::LLVMBackend,
};
use inkwell::{
    types::BasicType,
    values::{BasicValue, BasicValueEnum},
    AddressSpace,
};

impl<'ctx> LLVMBackend<'ctx> {
    /// Generate slice.len() method
    pub(in crate::llvm_backend) fn generate_slice_len_method(
        &mut self,
        var_name: &str,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Get the slice variable pointer from variable_map
        let slice_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .into_pointer_value();

        // Get the slice type
        let vector_type = self.variable_type_map.get(var_name).ok_or_else(|| {
            CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
        })?;

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type.clone()
        } else {
            return Err(CompilerError::internal("len() requires a slice variable"));
        };

        // Get the element LLVM type
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;

        // Create slice struct type
        let ptr_type = element_llvm_type.ptr_type(AddressSpace::default());
        let slice_struct_type = self.context.struct_type(
            &[
                ptr_type.into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        // Load current slice
        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;

        if !current_slice.is_struct_value() {
            return Err(CompilerError::internal("Slice value is not a struct"));
        }

        let slice_struct = current_slice.into_struct_value();

        // Extract len field from the slice struct
        let len = self
            .builder
            .build_extract_value(slice_struct, 1, "len")?
            .into_int_value();

        Ok(len.as_basic_value_enum())
    }

    /// Generate slice.cap() method
    pub(in crate::llvm_backend) fn generate_slice_cap_method(
        &mut self,
        var_name: &str,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Get the slice variable pointer from variable_map
        let slice_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .into_pointer_value();

        // Get the slice type
        let vector_type = self.variable_type_map.get(var_name).ok_or_else(|| {
            CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
        })?;

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type.clone()
        } else {
            return Err(CompilerError::internal("cap() requires a slice variable"));
        };

        // Get the element LLVM type
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;

        // Create slice struct type
        let ptr_type = element_llvm_type.ptr_type(AddressSpace::default());
        let slice_struct_type = self.context.struct_type(
            &[
                ptr_type.into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        // Load current slice
        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;

        if !current_slice.is_struct_value() {
            return Err(CompilerError::internal("Slice value is not a struct"));
        }

        let slice_struct = current_slice.into_struct_value();

        // Extract cap field from the slice struct
        let cap = self
            .builder
            .build_extract_value(slice_struct, 2, "cap")?
            .into_int_value();

        Ok(cap.as_basic_value_enum())
    }

    /// Generate slice.is_empty() method
    pub(in crate::llvm_backend) fn generate_slice_is_empty_method(
        &mut self,
        var_name: &str,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Get the slice variable pointer from variable_map
        let slice_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .into_pointer_value();

        // Get the slice type
        let vector_type = self.variable_type_map.get(var_name).ok_or_else(|| {
            CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
        })?;

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type.clone()
        } else {
            return Err(CompilerError::internal(
                "is_empty() requires a slice variable",
            ));
        };

        // Get the element LLVM type
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;

        // Create slice struct type
        let ptr_type = element_llvm_type.ptr_type(AddressSpace::default());
        let slice_struct_type = self.context.struct_type(
            &[
                ptr_type.into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        // Load current slice
        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;

        if !current_slice.is_struct_value() {
            return Err(CompilerError::internal("Slice value is not a struct"));
        }

        let slice_struct = current_slice.into_struct_value();

        // Extract len field from the slice struct
        let len = self
            .builder
            .build_extract_value(slice_struct, 1, "len")?
            .into_int_value();

        // Compare len with 0 to get boolean result
        let zero = self.context.i64_type().const_int(0, false);
        let is_empty =
            self.builder
                .build_int_compare(inkwell::IntPredicate::EQ, len, zero, "is_empty")?;

        Ok(is_empty.as_basic_value_enum())
    }

    /// Generate slice.contains(value) method - check if slice contains a value
    /// Returns true if the value is found, false otherwise
    pub(in crate::llvm_backend) fn generate_slice_contains_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "contains() requires exactly one argument (value)",
            ));
        }

        // Get the slice variable pointer from variable_map
        let slice_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .into_pointer_value();

        // Get the slice type
        let vector_type = self.variable_type_map.get(var_name).ok_or_else(|| {
            CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
        })?;

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type.clone()
        } else {
            return Err(CompilerError::internal(
                "contains() requires a slice variable",
            ));
        };

        // Get the element LLVM type
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;

        // Generate the search value
        let search_value = self.generate_expression(&arguments[0])?;

        // Create slice struct type
        let ptr_type = element_llvm_type.ptr_type(AddressSpace::default());
        let slice_struct_type = self.context.struct_type(
            &[
                ptr_type.into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        // Load current slice
        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;

        if !current_slice.is_struct_value() {
            return Err(CompilerError::internal("Slice value is not a struct"));
        }

        let slice_struct = current_slice.into_struct_value();

        // Extract fields from the slice struct
        let ptr = self
            .builder
            .build_extract_value(slice_struct, 0, "ptr")?
            .into_pointer_value();
        let len = self
            .builder
            .build_extract_value(slice_struct, 1, "len")?
            .into_int_value();

        // Create basic blocks for the loop
        let parent_fn = self
            .builder
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CompilerError::internal("No parent function for contains"))?;

        let loop_block = self.context.append_basic_block(parent_fn, "contains_loop");
        let body_block = self.context.append_basic_block(parent_fn, "contains_body");
        let found_block = self.context.append_basic_block(parent_fn, "contains_found");
        let not_found_block = self
            .context
            .append_basic_block(parent_fn, "contains_not_found");
        let merge_block = self.context.append_basic_block(parent_fn, "contains_merge");

        // Get the entry block before jumping
        let entry_block = self.builder.get_insert_block().unwrap();

        // Jump to loop
        let _ = self.builder.build_unconditional_branch(loop_block);

        // Loop block: check condition
        self.builder.position_at_end(loop_block);
        let i_phi = self.builder.build_phi(self.context.i64_type(), "i")?;
        let zero = self.context.i64_type().const_int(0, false);
        i_phi.add_incoming(&[(&zero, entry_block)]);
        let i = i_phi.as_basic_value().into_int_value();

        // Check if i < len
        let loop_cond =
            self.builder
                .build_int_compare(inkwell::IntPredicate::SLT, i, len, "loop_cond")?;

        // If i < len, go to body, else go to not_found
        let _ = self
            .builder
            .build_conditional_branch(loop_cond, body_block, not_found_block);

        // Body block: check if current element equals search value
        self.builder.position_at_end(body_block);

        // Get pointer to current element
        let element_ptr = unsafe {
            self.builder
                .build_gep(element_llvm_type, ptr, &[i], "element_ptr")?
        };

        // Load current element
        let current_element =
            self.builder
                .build_load(element_llvm_type, element_ptr, "current_element")?;

        // Compare current element with search value
        let is_equal = match element_type {
            Type::Basic(ref basic_type) => {
                match basic_type {
                    types::BasicType::Int8
                    | types::BasicType::Int16
                    | types::BasicType::Int32
                    | types::BasicType::Int
                    | types::BasicType::Int64
                    | types::BasicType::Uint8
                    | types::BasicType::Uint16
                    | types::BasicType::Uint32
                    | types::BasicType::Uint
                    | types::BasicType::Uint64
                    | types::BasicType::Bool => {
                        // Integer comparison
                        self.builder.build_int_compare(
                            inkwell::IntPredicate::EQ,
                            current_element.into_int_value(),
                            search_value.into_int_value(),
                            "is_equal",
                        )?
                    }
                    types::BasicType::Float32 | types::BasicType::Float64 => {
                        // Float comparison
                        self.builder.build_float_compare(
                            inkwell::FloatPredicate::OEQ,
                            current_element.into_float_value(),
                            search_value.into_float_value(),
                            "is_equal",
                        )?
                    }
                    _ => {
                        return Err(CompilerError::internal(
                            "contains() does not support this element type",
                        ));
                    }
                }
            }
            _ => {
                return Err(CompilerError::internal(
                    "contains() only supports basic types",
                ));
            }
        };

        // Increment i first (before conditional branch)
        let one = self.context.i64_type().const_int(1, false);
        let i_next = self.builder.build_int_add(i, one, "i_next")?;

        // If equal, go to found_block, else continue loop
        let _ = self
            .builder
            .build_conditional_branch(is_equal, found_block, loop_block);

        // Add incoming edge to PHI node for the loop continuation
        i_phi.add_incoming(&[(&i_next, body_block)]);

        // Found block: element found, return true
        self.builder.position_at_end(found_block);
        let true_val = self.context.bool_type().const_int(1, false);
        let _ = self.builder.build_unconditional_branch(merge_block);

        // Not found block: element not found, return false
        self.builder.position_at_end(not_found_block);
        let false_val = self.context.bool_type().const_int(0, false);
        let _ = self.builder.build_unconditional_branch(merge_block);

        // Merge block: create PHI node for result
        self.builder.position_at_end(merge_block);
        let result_phi = self
            .builder
            .build_phi(self.context.bool_type(), "contains_result")?;
        result_phi.add_incoming(&[(&true_val, found_block), (&false_val, not_found_block)]);

        Ok(result_phi.as_basic_value())
    }
}
